#pragma once

#include "Clause.h"
#include "PKB.h"
#include "ResultGraphTable.h"

/**
 *	Optimizer is a class that assigns and dynamically updates complexity values for the clauses given to it.
 *	It can be used to choose the least complex clause for evaluation.
 */ 
class Optimizer
{
public:
	typedef std::vector<Clause> ClauseSet;
	typedef std::vector<ClauseSet> ClauseSets;

	typedef Table<std::string, int> SynonymResultsTable;
	typedef std::unordered_map<std::string, SynonymResultsTable*> SynonymResultsTablePtrMap;


	/**---------------------------------------
	 *	User methods
	 *----------------------------------------*/
	/// Splits the given clause set up into independent clause sets and returns them.
	static ClauseSets splitIndependentClauses(const ClauseSet& clauses);
	/// Finds all clauses in the given set that have no synonyms, and returns this set.
	static ClauseSet extractSynonymlessClauses(ClauseSet *clauses);

	/**---------------------------------------
	 *	Constructors
	 *----------------------------------------*/
	/*! The main constructor that constructs an Optimizer with its set of clauses 
		and with a pkb pointer from which it can get data for complexity value assignment.
	*/
	Optimizer(const ClauseSet& clauses, SynonymTable *synTable, PKB *pkb);
	~Optimizer(void);
	
	/**---------------------------------------
	 *	Member methods
	 *----------------------------------------*/
	/// Returns true if there is at least 1 clause remaining.
	bool isAnyClauseLeft();
	/// Returns the clause with the least complexity.
	Clause getLeastComplexityClause();
	/// Removes the clause of least complexity.
	void removeLeastComplexityClause();
	/// Updates the complexity values of all the clauses by using information from the given ResultGraphTable.
	void updateComplexityValues(ResultGraphTable &resultGraphTable);
	/// Updates the complexity values of all the clauses by using information from the given tables.
	void updateComplexityValues(SynonymResultsTablePtrMap &tablePtrMap);
private:
	ClauseSet mClauses;
	SynonymTable *mSynTable;
	PKB *mPkb;
	int mLeastComplexityClause; // To store the index of the least complex clause. -1 if not yet found.

	// Initializes all the necessary complexities storage
	void initialize(PKB *pkb);
	// Initialization functions for each Time Complexities storage
	void initializeRelTimeComplexities(PKB *pkb);
	void initializePatTimeComplexities(PKB *pkb);
	void initializeConTimeComplexities(PKB *pkb);
	// Initialization functions for each Result Complexities storage
	void initializeRelResultComplexities(PKB *pkb);
	void initializePatResultComplexities(PKB *pkb);
	void initializeConResultComplexities(PKB *pkb);
	// Used for initializing time complexities storage
	// Returns the number of lines in the program
	unsigned int numLinesInProgram(PKB *pkb);
	// Returns the number of constants in the program
	unsigned int numConstantsInProgram(PKB *pkb);
	// Returns the number of procedures in the program
	unsigned int numProcsInProgram(PKB *pkb);
	// Returns the number of variables in the program
	unsigned int numVarsInProgram(PKB *pkb);
	// Returns the number of assign stmts in the program
	unsigned int numAssignsInProgram(PKB *pkb);
	// Returns the number of while stmts in the program
	unsigned int numWhilesInProgram(PKB *pkb);
	// Returns the number of if stmts in the program
	unsigned int numIfsInProgram(PKB *pkb);
	// Returns the number of call stmts in the program
	unsigned int numCallsInProgram(PKB *pkb);
	// Returns the largest number of stmts directly contained in a container stmt
	unsigned int largestNumberOfChildrenInOneParent(PKB *pkb);
	// Returns the maximum number of lines contained in any procedure
	unsigned int maxLinesInOneProcedure(PKB *pkb);
	/**---------------------------------------
	 *	Time Complexities Storage
	 *----------------------------------------*/
	// Time complexities for relationships
	std::unordered_map<RelationshipType, int> relTimeComplexities;
	// Time complexities for patterns
	std::unordered_map<PatternType, int> patTimeComplexities;
	// Time complexities for with-constraints
	std::unordered_map<ConstraintType, int> conTimeComplexities;

	/**---------------------------------------
	 *	Result Complexities Storage
	 *----------------------------------------*/
	// Result complexities for relationships
	std::unordered_map<RelationshipType, int> relResultComplexities;
	// Time complexities for patterns
	std::unordered_map<PatternType, int> patResultComplexities;
	// Time complexities for with-constraints
	std::unordered_map<ConstraintType, int> conResultComplexities;
	

	// Checks and returns true if the given index fulfills: 0 <= index < mClauses.size.
	bool isValidMClauseIndex(int index);
	// Finds the index of the least complexity clause
	int findLeastComplexityClause();

	// Computes the complexity value for the given relationship entry using the result graph table
	double computeComplexityValue(const RelationshipEntry& relationshipEntry, ResultGraphTable &rgt);
	// Computes the complexity value for the given pattern entry using the result graph table
	double computeComplexityValue(const PatternEntry& patternEntry, ResultGraphTable &rgt);
	// Computes the complexity value for the given constraint entry using the result graph table
	double computeComplexityValue(const ConstraintEntry& constraintEntry, ResultGraphTable &rgt);

	// Computes the complexity value for the given relationship entry using table
	double computeComplexityValue(const RelationshipEntry& relationshipEntry, SynonymResultsTablePtrMap &tablePtrMap);
	// Computes the complexity value for the given pattern entry using table
	double computeComplexityValue(const PatternEntry& patternEntry, SynonymResultsTablePtrMap &tablePtrMap);
	// Computes the complexity value for the given constraint entry using table
	double computeComplexityValue(const ConstraintEntry& constraintEntry, SynonymResultsTablePtrMap &tablePtrMap);

	// Returns true if the given clause does not have any synonym
	static bool isSynonymlessClause(const Clause& clause);
	// Maps the given clause based on the synonyms it has into the given clauseSetPtrsMap.
	// Returns the clause set pointer to which the given clause is mapped to.
	static ClauseSet* mapClause(const Clause &clause, std::unordered_map<std::string, ClauseSet*> *clauseSetPtrsMap);
	// Finds all the unique clause set ptrs for each of the given synonyms,
	// and puts them into the uniqueClauseSetPtrsMap.
	static void getAllUniqueClauseSetPtrs(const std::vector<std::string> &synonyms, 
		std::unordered_map<ClauseSet*, bool> *uniqueClauseSetPtrsMap, 
		const std::unordered_map<std::string, ClauseSet*> &clauseSetPtrsMap);
	// Finds all the synonyms in relEntry (if any) and puts them into the given synonyms set
	static void extractSynonymsOfRelationshipEntry(std::vector<std::string> *synonyms, const RelationshipEntry &relEntry);
	// Finds all the synonyms in patternEntry (if any) and puts them into the given synonyms set
	static void extractSynonymsOfPatternEntry(std::vector<std::string> *synonyms, const PatternEntry &patternEntry);
	// Finds all the synonyms in constraintEntry (if any) and puts them into the given synonyms set
	static void extractSynonymsOfConstraintEntry(std::vector<std::string> *synonyms, const ConstraintEntry &constraintEntry);

	// Returns the estimated number of tables between synonym1 and synonym2, using the given tablePtrMap.
	int getEstimatedNumTuplesFromTable(const std::string &synonym1, const std::string &synonym2, const SynonymResultsTablePtrMap &tablePtrMap);
	// Returns the number of indexes for the given synType, found with the PKB.
	int getNumIndexes(DesignEntityType synType);
};